package com.nervenets.shopping.service.impl;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
import com.alibaba.fastjson.JSON;
import com.github.binarywang.wxpay.bean.entpay.EntPayRequest;
import com.github.binarywang.wxpay.bean.entpay.EntPayResult;
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.nervenets.general.hibernate.DomainObject;
import com.nervenets.general.service.RedisService;
import com.nervenets.general.service.impl.BaseServiceImpl;
import com.nervenets.general.utils.JodaUtils;
import com.nervenets.general.utils.NumberUtil;
import com.nervenets.general.utils.StringUtils;
import com.nervenets.shopping.config.wx.WxMAConfiguration;
import com.nervenets.shopping.enumeration.OrderStatus;
import com.nervenets.shopping.hibernate.dao.GroupBuyOrderDao;
import com.nervenets.shopping.hibernate.domain.common.GlobalRuleSetting;
import com.nervenets.shopping.hibernate.domain.order.GroupBuyOrder;
import com.nervenets.shopping.hibernate.domain.order.GroupBuyOrderUser;
import com.nervenets.shopping.hibernate.domain.shopping.Product;
import com.nervenets.shopping.hibernate.domain.user.User;
import com.nervenets.shopping.hibernate.domain.user.UserWxId;
import com.nervenets.shopping.service.*;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.apache.curator.shaded.com.google.common.collect.Lists;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

import static com.nervenets.general.redis.RedisMultiPointLocker.lock;
import static com.nervenets.general.redis.RedisMultiPointLocker.unlock;

@Slf4j
@Service
public class GroupBuyOrderServiceImpl extends BaseServiceImpl<GroupBuyOrder, GroupBuyOrderDao> implements GroupBuyOrderService {
    @Resource
    private ProductService productService;
    @Resource
    private GlobalRuleService globalRuleService;
    @Resource
    private GroupBuyOrderUserService groupBuyOrderUserService;
    @Resource
    private GroupBuyOrderService groupBuyOrderService;
    @Resource
    private UserService userService;
    @Resource
    private WxPayService wxPayService;
    @Resource
    private DataOfWeekService dataOfWeekService;
    @Resource
    private RedisService redisService;

    @Override
    public boolean join(GroupBuyOrderUser order) {
        boolean grouped = false;
        long productId = order.getProductId();
        String key = StringUtils.formatKey("GroupBuyOrderJoin", productId);
        try {
            lock(key);
            Product product = productService.findById(productId).orElse(null);
            if (null != product) {
                GroupBuyOrder currentGroupBuy = product.getCurrentGroupBuy();
                if (null == currentGroupBuy || currentGroupBuy.isGrouped()) {
                    currentGroupBuy = GroupBuyOrder.builder()
                            .product(product)
                            .successCount(product.getGroupBuySuccess())
                            .failedAward(product.getGroupBuyFailedAward())
                            .build();
                    product.setCurrentGroupBuy(currentGroupBuy);
                }
                currentGroupBuy.getUsers().add(order);
                if (currentGroupBuy.isGrouped()) {
                    currentGroupBuy.setSuccessTime(JodaUtils.getTimestamp());
                    currentGroupBuy.setContribution(product.getGroupBuyContribution());
                    currentGroupBuy.setBonus(product.getGroupBuyBonus());
                    product.setCurrentGroupBuy(null);
                }
                super.save(currentGroupBuy);
                product.setGroupSuccessCount(groupBuyOrderService.count((predicates, root, query, builder) -> {
                    predicates.add(builder.equal(root.get("product").get("id"), product.getId()));
                    predicates.add(builder.greaterThan(root.get("successTime"), 0));
                }));
                productService.save(product);
                final long startOfThisWeek = JodaUtils.getStartOfThisWeek();
                long endOfThisWeek = JodaUtils.getEndOfThisWeek();
                dataOfWeekService.updateData(startOfThisWeek, count((predicates, root, query, builder) -> {
                    predicates.add(builder.greaterThan(root.get("successTime"), 0));
                    predicates.add(builder.between(root.get("successTime"), startOfThisWeek, endOfThisWeek));
                }));
                grouped = currentGroupBuy.isGrouped();
            }
        } finally {
            unlock(key);
        }
        return grouped;
    }

    /**
     * 是否中签的规则
     *
     * @param setting 全局配置规则
     * @param user    用户
     * @return 是否中签
     */
    private boolean isBingo(GlobalRuleSetting setting, User user) {
        if (null == user) return false;
        long startOfToday = JodaUtils.getStartOfToday();
        if (redisService.hasKey(String.format("bingo-%s-%s", user.getId(), startOfToday))) return false;
        if (user.isExcellentInviter()) {
            return setting.isExcellentInviterBingo();
        } else if (user.isInviter()) {
            if (setting.isInviterBingo()) {
                return user.getInviteCount() < setting.getInviterNotBingoCount();
            } else {
                return user.getInviteCount() < setting.getInviterBingoInviteCount();
            }
        } else {
            if (user.getCreateTime() >= startOfToday) return false;
            return user.getInviteCount() < setting.getUserNotBingoInviteCount();
        }
    }

    /**
     * 筛选优秀团长
     *
     * @param setting 全局配置规则
     * @param user    用户
     * @return 是否中签
     */
    private boolean isExcellentInviterBingo(GlobalRuleSetting setting, User user) {
        if (user.isExcellentInviter()) {
            return setting.isExcellentInviterBingo();
        }
        return !redisService.hasKey(String.format("bingo-%s", user.getId()));
    }

    @Override
    public void successHandle() {
        List<GroupBuyOrder> orders = findAll((predicates, root, query, builder) -> {
            predicates.add(builder.greaterThan(root.get("successTime"), 0));
            predicates.add(builder.equal(root.get("handleTime"), 0));
        });

        for (GroupBuyOrder order : orders) {
            GlobalRuleSetting setting = globalRuleService.getGlobalRule().getSetting();
            log.info("开始处理订单中签：" + order.getId());
            log.info("订单处理时间：" + JodaUtils.timeLongToString(order.getHandleTime()));
            List<Long> userIds = new ArrayList<>();
            if (order.getHandleTime() == 0) {
                userIds = order.getUsers().stream().map(DomainObject::getCreateBy).filter(l -> isBingo(setting, userService.fastFind(l))).collect(Collectors.toList());
                log.info("中标首次筛选：" + JSON.toJSONString(userIds));
                //去重
                userIds = distinct(userIds);
                log.info("中标去重筛选：" + JSON.toJSONString(userIds));
                //如果全是邀请者或产生的中标者不够，则不考虑规则。
                if (userIds.isEmpty() || userIds.size() < order.getProduct().getGroupBuySuccessUser()) {
                    userIds = order.getUsers().stream().map(DomainObject::getCreateBy).filter(l -> isExcellentInviterBingo(setting, userService.fastFind(l))).collect(Collectors.toList());
                    log.info("中标者不够：" + JSON.toJSONString(userIds));
                    userIds = distinct(userIds);
                    log.info("中标去重筛选：" + JSON.toJSONString(userIds));

                    if (userIds.size() < order.getProduct().getGroupBuySuccessUser()) {
                        log.info("中标去掉优秀团长还不够，只能随机所有的了：" + JSON.toJSONString(userIds));
                        userIds = order.getUsers().stream().map(DomainObject::getCreateBy).collect(Collectors.toList());
                    }
                }
                //打乱
                if (userIds.size() > 1) {
                    Collections.shuffle(userIds);
                }
                //List<User> users = userService.findAllById(userIds);
                //users.sort(Comparator.comparingLong(User::getInviteCount));
                //log.info("中标后排序：" + JSON.toJSONString(users));

                if (userIds.size() > order.getProduct().getGroupBuySuccessUser()) {
                    userIds = userIds.subList(0, order.getProduct().getGroupBuySuccessUser());
                    log.info("中标者太多：" + JSON.toJSONString(userIds));
                }
                //userIds = users.stream().map(DomainObject::getId).collect(Collectors.toList());
                log.info("中标用户：" + JSON.toJSONString(userIds));
            }

            HashMap<Long, Boolean> executed = new HashMap<>();
            WxMaService maService = WxMAConfiguration.getMaService(0);

            for (GroupBuyOrderUser orderUser : order.getUsers()) {
                orderUser.setGroupedTime(JodaUtils.getTimestamp());
                long createBy = orderUser.getCreateBy();
                if (userIds.contains(createBy) && !executed.containsKey(createBy)) {
                    orderUser.setStatus(OrderStatus.grouped);
                    orderUser.setBingo(true);
                    executed.put(createBy, Boolean.TRUE);

                    redisService.set(String.format("bingo-%s-%s", createBy, JodaUtils.getStartOfToday()), String.valueOf(orderUser.getId()), 24 * 60 * 60);

                    UserWxId userWxId = userService.findUserWxId(userService.findOne(createBy), maService.getWxMaConfig().getAppid());
                    if (null != userWxId) {
                        try {
                            maService.getMsgService().sendSubscribeMsg(WxMaSubscribeMessage.builder()
                                    .templateId("leN8hTHndxwTEVYCZRs1rOhqxK_FieHlXuBSaX6TOQ4")
                                    .data(Lists.newArrayList(
                                            new WxMaSubscribeMessage.MsgData("thing1", order.getProductName()),
                                            new WxMaSubscribeMessage.MsgData("amount2", NumberUtil.fen2yuan(orderUser.getProduct().getPrice()) + "元"),
                                            new WxMaSubscribeMessage.MsgData("thing3", order.getSuccessCount() + "人"),
                                            new WxMaSubscribeMessage.MsgData("thing5", "请注意查收。")
                                    ))
                                    .toUser(userWxId.getOpenId())
                                    .build());
                        } catch (WxErrorException e) {
                            e.printStackTrace();
                            log.error("sendSubscribeMsg", e);
                        }
                    }
                } else {
                    if (orderUser.isBingo()) continue;
                    if (orderUser.getRefundTime() > 0) {
                        rewardHandler(orderUser);
                    } else if (StringUtils.isEmpty(orderUser.getOutTradeNo())) {
                        orderUser.setStatus(OrderStatus.refund);
                        orderUser.setRefundTime(JodaUtils.getTimestamp());
                        orderUser.setReward(Math.max(order.getProduct().getGroupBuyFailedAward(), 0));
                        orderUser.addLog("拼团失败退款成功");
                    } else {
                        WxPayRefundRequest request = new WxPayRefundRequest();
                        request.setTransactionId(orderUser.getOutTradeNo());
                        request.setOutRefundNo(orderUser.getTradeNo());
                        request.setTotalFee(orderUser.getMoney());
                        request.setRefundFee(orderUser.getMoney());
                        request.setRefundDesc("很遗憾，拼团失败，感谢您的参与。");
                        WxPayRefundResult refundResult;
                        try {
                            refundResult = wxPayService.refund(request);
                            if ("SUCCESS".equals(refundResult.getReturnCode()) && "SUCCESS".equals(refundResult.getResultCode())) {
                                orderUser.setStatus(OrderStatus.refund);
                                orderUser.setRefundTime(JodaUtils.getTimestamp());
                                orderUser.addLog("拼团失败退款成功");

                                if (order.getProduct().getGroupBuyFailedAward() > 0) {
                                    orderUser.setReward(Math.max(order.getProduct().getGroupBuyFailedAward(), 0));
                                }
                                if (orderUser.getReward() > 0) {
                                    try {
                                        Thread.sleep(500);
                                    } catch (Exception ignored) {
                                    }
                                    rewardHandler(orderUser);
                                }
                            } else {
                                orderUser.addLog("拼团失败退款失败：" + refundResult.getErrCodeDes());
                            }
                        } catch (WxPayException e) {
                            orderUser.addLog("拼团失败退款异常：" + e.getErrCodeDes());
                        }
                    }
                }
            }
            order.setHandleTime(JodaUtils.getTimestamp());
            super.save(order);
        }
    }

    @Override
    public long sumOfContribution() {
        return dao.sumOfContribution().longValue();
    }

    @Override
    public long sumOfContribution(long startTime, long endTime) {
        return dao.sumOfContribution(startTime, endTime).longValue();
    }

    @Override
    public long sumOfBonus() {
        return dao.sumOfBonus().longValue();
    }

    @Override
    public long sumOfBonus(long startTime, long endTime) {
        return dao.sumOfBonus(startTime, endTime).longValue();
    }

    @Override
    public BigDecimal sumOfBonusByTime(long startTime, long endTime) {
        return dao.sumOfBonusByTime(startTime, endTime);
    }

    private void rewardHandler(GroupBuyOrderUser orderUser) {
        if (0 == orderUser.getRewardTime()) {
            UserWxId userWxId = userService.findUserWxId(userService.findOne(orderUser.getCreateBy()), wxPayService.getConfig().getAppId());
            if (null != userWxId) {
                EntPayRequest entPayRequest = EntPayRequest.newBuilder()
                        .partnerTradeNo(orderUser.getTradeNo())
                        .openid(userWxId.getOpenId())
                        .checkName("NO_CHECK")
                        .amount(orderUser.getReward())
                        .description("拼团失败，感谢您的参与及推广！")
                        .spbillCreateIp("121.199.15.222")
                        .build();
                EntPayResult entPayResult;
                try {
                    entPayResult = wxPayService.getEntPayService().entPay(entPayRequest);
                    if ("SUCCESS".equals(entPayResult.getReturnCode()) && "SUCCESS".equals(entPayResult.getResultCode())) {
                        orderUser.setRewardTime(JodaUtils.getTimestamp());
                        orderUser.addLog("拼团失败奖励发放成功");
                    } else {
                        orderUser.addLog("拼团失败奖励失败：" + entPayResult.getErrCodeDes());
                    }
                } catch (WxPayException e) {
                    orderUser.addLog("拼团失败奖励异常：" + e.getErrCodeDes());
                }
            }
        }
    }

    private <T> List<T> distinct(Collection<T> datas) {
        if (datas == null) {
            return new ArrayList<>();
        }
        Set<T> set = new HashSet<>(datas);//使用HashSet构造方法去重
        return new ArrayList<>(set);
    }
}
